1 module repl;
2 
3 import std.algorithm;
4 import pegged.grammar;
5 import sumtype;
6 import std.format;
7 import std.conv;
8 import std.traits;
9 import std.meta;
10 import std.typecons;
11 
12 mixin(grammar(`
13 ToyLanguage:
14     Statements < ((AssignmentExpression / Expression) (EndOfLine?))+ eoi
15 
16     AssignmentExpression < Expression "=" (Value / Expression)
17 
18     Expression < (Invoke / Identifier / IncompleteIdentifier) ("." (Invoke / Identifier / IncompleteIdentifier))*
19 
20     Invoke <- Identifier ("()")
21 
22     Whitespace <- :(' ' / '\t')+
23 
24     EndOfLine <: ('\r' '\n') / '\n'
25 
26     Spacing <- :(Whitespace)*
27 
28     Identifier <~ '_'? [A-Za-z] [0-9A-Z_a-z-]*
29 
30     IncompleteIdentifier < eps
31 
32     Integer <~ '-'? ([1-9] [0-9]*) / ('0' [Xx] [0-9A-Fa-f]+) / ('0' [0-7]*)
33 
34     Float <~ '-'? ((([0-9]+ '.' [0-9]*) / ([0-9]* '.' [0-9]+))(('E' / 'e') ('+' / '-')? [0-9]+)?) / ([0-9]+ ('E' / 'e') ('+' / '-')? [0-9]+)
35 
36     String <~ "\"" (!("\"" / EndOfLine) .)* "\""
37 
38     Value <- String / Float / Integer
39 `));
40 
41 auto parseToyLanguage(string code) @trusted {
42     return ToyLanguage(code);
43 }
44 
45 @safe unittest {
46     assert(parseToyLanguage("a").successful);
47     assert(parseToyLanguage("a = 4").successful);
48     assert(parseToyLanguage(`a = "text"`).successful);
49     assert(parseToyLanguage("a = FloatWidget()").successful);
50     assert(parseToyLanguage("a.max = 45.3").successful);
51     assert(parseToyLanguage("a.display()").successful);
52     assert(parseToyLanguage(`a = FloatWidget()
53   a.max = 45.3
54   a.display()`).successful);
55 }
56 
57 version (unittest) {
58     struct Foo {
59         int a;
60         void doStuff() @safe {a = 5; }
61         void set(int b) @safe { a = b; }
62     }
63     struct Inner {
64         int b = 1;
65         void incr() @safe { b += 1; }
66     }
67     struct Bar {
68         Inner inner;
69     }
70 }
71 
72 @safe unittest {
73     Repl!(Foo) repl;
74 
75     repl.run("a");
76     repl.run("a = Foo()");
77     assert(repl.run("a.a").value == repl.Value(0));
78     repl.run("a.doStuff()");
79     assert(repl.run("a.a").value == repl.Value(5));
80 }
81 
82 @safe unittest {
83     Repl!(Bar) repl;
84 
85     repl.run(`a = Bar()`);
86     assert(repl.run(`a.inner.b`).value == repl.Value(1));
87     repl.run(`a.inner.incr()`);
88     assert(repl.run(`a.inner.b`).value == repl.Value(2));
89     assert(repl.run(`a.inner.b = 4`).value == repl.Value(4));
90     repl.run(`a.inner.incr()`);
91     assert(repl.run(`a.inner.b`).value == repl.Value(5));
92 }
93 
94 @safe unittest {
95     Repl!(Foo) repl;
96 
97     repl.addGlobal("foo", Foo(4));
98     assert(repl.run("foo.a").value == repl.Value(4));
99 }
100 
101 @safe unittest {
102     Repl!(Foo) repl;
103 
104     repl.run("a = Foo()");
105     assert(repl.run("a.a = 43.3").error == true);
106     assert(repl.run("a.a = 43.3").message == "Error: cannot assign type double to type int in 'a.a = 43.3'");
107     assert(repl.run("a.noexist").error == true);
108     assert(repl.run("a.noexist").message == "Error: property 'noexist' is not available on type Foo in 'noexist'");
109     assert(repl.run("a.noexist()").error == true);
110     assert(repl.run("a.noexist()").message == "Error: method 'noexist' is not available on type Foo in 'noexist()'");
111     assert(repl.run("Nope()").error == true);
112     assert(repl.run("Nope()").message == "Error: type Nope not found in 'Nope()'");
113     assert(repl.run("a.a.impossible").error == true);
114     assert(repl.run("a.a.impossible").message == "Error: cannot access a property on a int in 'impossible'");
115     assert(repl.run("a.a.impossible()").error == true);
116     assert(repl.run("a.a.impossible()").message == "Error: cannot call a method on a int in 'impossible()'");
117 }
118 
119 @safe unittest {
120     Repl!(Foo) repl;
121     int b = 88;
122     repl.run("a = Foo()");
123     repl.run("a.set()", b);
124     assert(repl.run("a.a").value == repl.Value(88));
125 }
126 
127 @safe unittest {
128     Repl!(Bar) repl;
129 
130     repl.addGlobal("widgets", 4);
131     assert(repl.complete("", 0) == CompleteResult(["widgets"], 0, 0));
132     assert(repl.complete("w", 0) == CompleteResult(["widgets"], 0, 1));
133 }
134 
135 @safe unittest {
136     Repl!(Bar) repl;
137 
138     repl.addGlobal("bar", Bar());
139     assert(repl.complete("b",1) == CompleteResult(["bar"], 0, 1));
140     assert(repl.complete("bar.",4) == CompleteResult(["inner"], 4, 4));
141     assert(repl.complete("bar.in",5) == CompleteResult(["inner"], 4, 6));
142 }
143 
144 @safe unittest {
145     Repl!(Bar) repl;
146 
147     assert(repl.complete("b = Bar()
148 b. = 4
149 c = Bar()",13) == CompleteResult(["inner"], 13, 13));
150     assert(repl.complete("b = Bar()
151 b.inner. = 4
152 c = Bar()",15) == CompleteResult(["b", "incr"], 19, 19));
153     assert(repl.complete("b = Bar()
154 b.inn = 4
155 c = Bar()",14) == CompleteResult(["inner"], 12, 16));
156 }
157 
158 struct CompleteResult {
159     string[] matches;
160     ulong start;
161     ulong end;
162 }
163 
164 struct Repl(Ts...) if (Ts.length > 0) {
165     alias makePointerType(T) = T*;
166     alias InnerTypes = discoverTypes!(Ts, double, int, string);
167     alias InnerTypesPtrs = staticMap!(makePointerType, AliasSeq!(InnerTypes, Ts));
168     alias Value = SumType!(Ts, InnerTypes, InnerTypesPtrs, Void);
169 
170     struct Void {
171     }
172 
173     class Var {
174         Value value;
175         ParseTree tree;
176         bool error;
177         bool valid;
178         string message;
179 
180         this(ParseTree tree) @safe {
181             this.tree = tree;
182             value = Void();
183         }
184 
185         this(Value value) @safe {
186             this.value = value;
187             valid = true;
188         }
189 
190         this(ParseTree tree, Value value) @safe {
191             this(tree);
192             this.replaceValue(value);
193             valid = true;
194         }
195 
196         void toString(scope void delegate(const(char)[]) sink) const @trusted {
197             sink("tree: ");
198             if (tree.input.length > 0)
199                 sink(tree.text());
200             else
201                 sink("\n");
202             sink("value: ");
203             sink(value.text());
204             sink("\nerror: ");
205             sink(error.text());
206             sink("\nvalid: ");
207             sink(valid.text());
208             sink("\nmessage: ");
209             sink(message);
210             sink("\n");
211         }
212 
213         Var call(Ps...)(ParseTree tree, Ps ps) {
214             assert(tree.name == "ToyLanguage.Invoke");
215             auto name = tree.matches[0];
216             if (!valid)
217                 return this;
218             try {
219                 return new Var(tree, value.match!((ref t) => callIt(t, name, ps)));
220             } catch (Exception e) {
221                 return Var.createError(tree, e);
222             }
223         }
224 
225         Var access(ParseTree tree) @safe {
226             assert(tree.name == "ToyLanguage.Identifier");
227             auto name = tree.matches[0];
228             if (!valid)
229                 return this;
230             try {
231                 return new Var(tree, value.match!((ref t) => accessIt(t, name)));
232             } catch (Exception e) {
233                 return new Var(tree, value).addError(tree, e);
234             }
235         }
236 
237         Var assign(ParseTree tree, Var other) @safe {
238             assert(tree.name == "ToyLanguage.AssignmentExpression");
239             if (!valid) {
240                 replaceValue(other.value);
241                 valid = true;
242                 return this;
243             }
244             try {
245                 return replaceValue(value.match!((ref t) => assignIt(t, other)));
246             } catch (Exception e) {
247                 return this.addError(tree, e);
248             }
249         }
250 
251         static Var createError(ParseTree tree, Exception e) @trusted {
252             return Var.createError(tree, cast(string)e.message);
253         }
254 
255         static Var createError(ParseTree tree, string message) @safe {
256             return new Var(tree).addError(tree, message);
257         }
258 
259         Var addError(ParseTree tree, string message) @safe {
260             this.message = "Error: %s in '%s'".format(message, tree.input[tree.begin .. tree.end]);
261             this.error = true;
262             return this;
263         }
264 
265         Var addError(ParseTree tree, Exception e) @trusted {
266             return this.addError(tree, cast(string)e.message);
267         }
268 
269         Var replaceValue(Value v) @trusted {
270             this.value = v;
271             return this;
272         }
273 
274         string source() @safe {
275             return tree.input[tree.begin .. tree.end];
276         }
277     }
278     Var[string] symbols;
279 
280     void addGlobal(T)(string name, T v) {
281         auto var = new Var(Value(v));
282         symbols[name] = var;
283     }
284 
285     Var run(Ps...)(string code, Ps ps) {
286         auto result = parseToyLanguage(code);
287         if (!result.successful)
288             throw new Exception(result.matches.joiner("\n").text());
289 
290         return result.children.map!(child => executeStatements(child, ps)).joiner().reduce!((a,b) => a.error ? a : b);
291     }
292 
293     CompleteResult complete(Ps...)(string code, ulong cursorPos, Ps ps) {
294         import std.array : array;
295 
296         auto result = parseToyLanguage(code);
297         auto statements = result.children.map!(child => executeStatements(child, ps)).joiner().array();
298         auto s = statements.filter!(s => s.tree.begin <= cursorPos && s.tree.end >= cursorPos);
299 
300         if (s.empty)
301             return CompleteResult(getAllValidSymbols(), 0, code.length);
302 
303         auto statement = s.front;
304 
305         if (!statement.valid) {
306             return CompleteResult(getAllValidSymbols(), statement.tree.begin, statement.tree.end);
307         }
308 
309         auto deepest = getLastChild(statement.tree);
310         auto matches = statement.value.match!((ref t) => getFieldsAndFunctions(t));
311 
312         return CompleteResult(matches, deepest.begin, deepest.end);
313     }
314 
315 private:
316     string[] getAllValidSymbols() @safe {
317         import std.array : array;
318         return symbols.byKeyValue.filter!(pair => pair.value.valid).map!(pair => pair.key).array();
319     }
320 
321     Var assignToVar(Ps...)(ParseTree tree, Var lhs, Var rhs, Ps ps) {
322         if (lhs.error)
323             return lhs;
324         if (rhs.error)
325             return rhs;
326         if (!rhs.valid)
327             return rhs.addError(tree, "%s is unitialized".format(rhs.source));
328         return lhs.assign(tree, rhs);
329     }
330 
331     auto executeStatements(Ps...)(ParseTree tree, Ps ps) {
332         assert(tree.name == "ToyLanguage.Statements");
333         return tree.children.map!(child => executeStatement(child, ps));
334     }
335 
336     Var dereference(Ps...)(Var var, Ps ps) {
337         return var.replaceValue(var.value.match!((ref t){
338                     static if (isPointer!(typeof(t))) {
339                         return Value(*t);
340                     } else
341                         return Value(t);
342                 }));
343     }
344 
345     Var executeStatement(Ps...)(ParseTree tree, Ps ps) {
346         if (!tree.successful)
347             return Var.createError(tree, tree.name);
348         switch (tree.name) {
349         case "ToyLanguage.AssignmentExpression": return dereference(executeAssignment(tree, ps));
350         case "ToyLanguage.Expression": return dereference(evaluateExpression(tree, ps));
351         default: assert(0);
352         }
353     }
354 
355     Var executeAssignment(Ps...)(ParseTree tree, Ps ps) {
356         assert(tree.name == "ToyLanguage.AssignmentExpression");
357         return assignToVar(tree, getVar(tree.children[0], ps), getVar(tree.children[1], ps));
358     }
359 
360     Var getVar(Ps...)(ParseTree tree, Ps ps) {
361         assert(tree.name == "ToyLanguage.Expression" || tree.name == "ToyLanguage.Value");
362         if (tree.name == "ToyLanguage.Value")
363             return getValueVar(tree, ps);
364         return evaluateExpression(tree, ps);
365     }
366 
367     Var getValueVar(Ps...)(ParseTree tree, Ps ps) {
368         assert(tree.name == "ToyLanguage.Value");
369         switch (tree.children[0].name) {
370         case "ToyLanguage.Integer": return new Var(tree, Value(tree.matches[0].to!int));
371         case "ToyLanguage.Float": return new Var(tree, Value(tree.matches[0].to!double));
372         case "ToyLanguage.String": return new Var(tree, Value(tree.matches[0][1..$-1]));
373         default: assert(0);
374         }
375     }
376 
377     Var evaluateExpression(Ps...)(ParseTree tree, Ps ps) {
378         assert(tree.name == "ToyLanguage.Expression");
379         Var object = evaluateGlobal(tree.children[0], ps);
380         foreach (child; tree.children[1 .. $]) {
381             if (child.name == "ToyLanguage.Invoke")
382                 object = object.call(child, ps);
383             else if (child.name == "ToyLanguage.Identifier")
384                 object = object.access(child);
385             else {
386                 auto o = new Var(tree,object.value);
387                 return o.addError(tree, "Invalid");
388             }
389         }
390         return object;
391     }
392 
393     Var evaluateGlobal(Ps...)(ParseTree tree, Ps ps) {
394         assert(tree.name == "ToyLanguage.Invoke" || tree.name == "ToyLanguage.Identifier");
395         if (tree.name == "ToyLanguage.Invoke") {
396             static foreach(T; Ts) {
397                 static if (isAggregateType!T) {
398                     if (__traits(identifier, T) == tree.matches[0])
399                         return new Var(Value(T.init));
400                 }
401             }
402             return Var.createError(tree, "type %s not found".format(tree.matches[0]));
403         }
404         if (auto p = tree.matches[0] in symbols)
405             return (*p);
406         auto object = new Var(tree);
407         symbols[tree.matches[0]] = object;
408         return object;
409     }
410 
411     static auto callIt(T, Ps...)(ref T t, string name, Ps ps) {
412         alias BaseType = getBaseType!T;
413         static if (isAggregateType!BaseType || is(BaseType == class)) {
414             static foreach(fun; getFunctions!(BaseType)) {{
415                     enum funName = __traits(identifier, fun);
416                     if (name == funName) {
417                         alias returnType = ReturnType!(__traits(getMember, t, funName));
418                         static if (is(returnType == void)) {
419                             callFunction!(T, void, funName, Ps)(t, ps);
420                             return Value(Void());
421                         } else {
422                             return Value(callFunction!(T, returnType, funName, Ps)(t, ps));
423                         }
424                     }
425                 }}
426             throw new Exception("method '%s' is not available on type %s".format(name, __traits(identifier, BaseType)));
427         } else
428             throw new Exception("cannot call a method on a %s".format(BaseType.stringof));
429         return Value(Void()); // needed for inference
430     }
431 
432     static auto accessIt(T)(ref T t, string name) {
433         alias BaseType = getBaseType!(T);
434         static if (isAggregateType!BaseType && !is(BaseType : Nullable!P, P)) {
435             static foreach(member; getFields!BaseType) {
436                 if (name == member) {
437                     return Value(&__traits(getMember, t, member));
438                 }
439             }
440             throw new Exception("property '%s' is not available on type %s".format(name, __traits(identifier, BaseType)));
441         } else
442             throw new Exception("cannot access a property on a %s".format(BaseType.stringof));
443         return Value(Void()); // needed for inference
444     }
445 
446     static auto assignIt(T)(ref T t, Var other) {
447         static if (isPointer!T) {
448             other.value.match!((T otherT){
449                     *t = *otherT;
450                 },(ref PointerTarget!T otherT){
451                     *t = otherT;
452                 },(ref incompatible){
453                     throw new Exception("cannot assign type %s to type %s".format(typeof(incompatible).stringof, PointerTarget!(T).stringof));
454                 });
455             return Value(t);
456         } else
457             return other.value;
458     }
459 
460     static string[] getFieldsAndFunctions(T)(ref T t) {
461         static if (is(T == Void))
462             return [];
463         else static if (isAggregateType!T || is(T == class)) {
464             alias Fields = getFields!T;
465             alias FunctionSymbols = getFunctions!T;
466             alias Functions = staticMap!(getIdentifier, FunctionSymbols);
467             static if (Fields.length == 0 && Functions.length == 0)
468                 return [];
469             else static if (Fields.length == 0) {
470                 enum string[Functions.length] fieldsAndFunctions = [Functions];
471             } else static if (Functions.length == 0) {
472                 enum string[Fields.length] fieldsAndFunctions = [Fields];
473             } else
474                 enum string[Fields.length + Functions.length] fieldsAndFunctions = [Fields, Functions];
475             return fieldsAndFunctions[];
476         } else {
477             return [];
478         }
479     }
480 
481     static R callFunction(T, R, string name, Ps...)(auto ref T t, Ps ps) {
482         alias fun = __traits(getMember, t, name);
483         alias parameters = Parameters!(fun);
484         alias ResultType = ReturnType!(fun);
485         static if (parameters.length > 0) {
486             static if (__traits(compiles, __traits(getMember, t, name)(ps))) {
487                 return __traits(getMember, t, name)(ps);
488             }
489             throw new Exception("cannot call method '%s' on type %s".format(name, T.stringof));
490         } else {
491             return __traits(getMember, t, name)();
492         }
493     }
494 }
495 
496 @safe unittest {
497     Foo foo = Foo();
498     Repl!(Foo).callIt(foo, "doStuff");
499     assert(foo.a == 5);
500 }
501 
502 // get the most deepest, last child of the tree
503 ParseTree getLastChild(ref ParseTree p) @safe {
504     if (p.children.length == 0)
505         return p;
506     return getLastChild(p.children[$-1]);
507 }
508 
509 // strips the pointer
510 template getBaseType(T) {
511     static if (isPointer!T)
512         alias getBaseType = PointerTarget!T;
513     else
514         alias getBaseType = T;
515 }
516 
517 enum getIdentifier(alias T) = __traits(identifier, T);
518 
519 // gets all functions from T as symbols
520 template getFunctions(T) {
521     alias getSymbol(string name) = AliasSeq!(__traits(getMember, T, name))[0];
522     enum canAlias(string name) = __traits(compiles, getSymbol!name);
523     alias members = __traits(allMembers, T);
524     alias hasTSymbol = ApplyLeft!(hasMember, T);
525     alias symbols = staticMap!(getSymbol, Filter!(canAlias, Filter!(hasTSymbol, AliasSeq!(members))));
526     alias getFunctions = Filter!(isFunction, symbols);
527 }
528 
529 // returns all public fields of T
530 template getFields(T) {
531     alias hasField(string name) = hasMember!(T, name);
532     enum isPublic(string name) = __traits(getProtection, __traits(getMember, T, name)) == "public";
533     alias getFields = Filter!(isPublic, Filter!(hasField, FieldNameTuple!(T)));
534 }
535 
536 // Recursively get all types of the fields and return values of each function of each item in Ts
537 template discoverTypes(Ts...) {
538     enum isNotVoid(T) = !is(T == void);
539     static if (Ts.length == 0) {
540         alias discoverTypes = AliasSeq!();
541     } else static if (Ts.length == 1) {
542         alias T = Ts[0];
543         static if (!isAggregateType!T && !(is(T == class))) {
544             alias discoverTypes = AliasSeq!T;
545         } else static if(is(T : Nullable!P, P)) {
546             alias discoverTypes = AliasSeq!(Nullable!P, discoverTypes!P);
547         } else {
548             alias typeOfField(string name) = typeof(__traits(getMember, T, name));
549             alias hasField(string name) = hasMember!(T, name);
550             enum isNoT(Other) = !is(T == Other);
551             alias fieldTypes = Filter!(isNoT, staticMap!(typeOfField, getFields!T));
552             alias functionReturnTypes = Filter!(isNoT, staticMap!(ReturnType, getFunctions!(T)));
553             alias Types = AliasSeq!(fieldTypes, discoverTypes!fieldTypes, functionReturnTypes, discoverTypes!(functionReturnTypes));
554             alias discoverTypes = Filter!(isNotVoid, NoDuplicates!(Types));
555         }
556     } else {
557         alias discoverTypes = Filter!(isNotVoid, NoDuplicates!(AliasSeq!(discoverTypes!(Ts[0]), discoverTypes!(Ts[1 .. $]))));
558     }
559 }
560